package edu.whu.rose.clock.kps.algorithm.blinks.index;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;

import com.sleepycat.bind.tuple.DoubleBinding;
import com.sleepycat.bind.tuple.IntegerBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

import edu.whu.rose.clock.kps.algorithm.blinks.binding.BlockKeywordBinding;
import edu.whu.rose.clock.kps.algorithm.blinks.binding.BlockVertexKeywordBinding;
import edu.whu.rose.clock.kps.algorithm.blinks.binding.KNListBinding;
import edu.whu.rose.clock.kps.algorithm.blinks.binding.NKMapBinding;
import edu.whu.rose.clock.kps.algorithm.blinks.binding.PNListBinding;
import edu.whu.rose.clock.kps.datastruct.SimpleSubgraphVertex;
import edu.whu.rose.clock.ssql.binding.IntArrayBinding;
import edu.whu.rose.clock.ssql.binding.SubGraphBinding;
import edu.whu.rose.clock.ssql.binding.SubgraphVertexBinding;

public class BilevelIndexDB {

	private String dbEnv;
	private Environment env;
	
	private Database keyword_block;
	private Database portal_block;
	
	private Database keyword_node;
	private Database node_keyword;
	private Database portal_node;
	private Database node_portal;
	
	public BilevelIndexDB() {
		this.dbEnv = "dbEnv_bilevel";
	}
	
	public BilevelIndexDB(String dbEnv) {
		this.dbEnv = dbEnv;
	}
	
	public boolean initEnv() {
		EnvironmentConfig envConfig = new EnvironmentConfig();
		envConfig.setAllowCreate(true);
		try {
			this.env = new Environment(new File(dbEnv), envConfig);
			return true;
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: opening database environment");
			return false;
		}
	}
	
	public boolean openDB() {
		try {
			DatabaseConfig dbConfig = new DatabaseConfig();
			dbConfig.setAllowCreate(true);
			keyword_block = env.openDatabase(null, "keyword_block", dbConfig);
			portal_block = env.openDatabase(null, "portal_block", dbConfig);
			keyword_node = env.openDatabase(null, "keyword_node", dbConfig);
			node_keyword = env.openDatabase(null, "node_keyword", dbConfig);
			portal_node = env.openDatabase(null, "portal_node", dbConfig);
			node_portal = env.openDatabase(null, "node_portal", dbConfig);
			return true;
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: opening databases");
			return false;
		}
	}
	
	public void closeDB() {
		try {
			if (keyword_block != null) {
				keyword_block.close();
			}
			if (portal_block != null) {
				portal_block.close();
			}
			if (keyword_node != null) {
				keyword_node.close();
			}
			if (node_keyword != null) {
				node_keyword.close();
			}
			if (portal_node != null) {
				portal_node.close();
			}
			if (node_portal != null) {
				node_portal.close();
			}
			if (env != null) {
				env.close();
			}
		}
		catch (DatabaseException ex) {
			System.err.println("Error: closing databases and environment.");
			ex.printStackTrace();
		}
	}
	
	public void clearDB(int db) {
		Database database = null;
		switch (db) {
			case 1: database = keyword_block; break;
			case 2: database = portal_block; break;
			case 3: database = keyword_node; break;
			case 4: database = node_keyword; break;
			case 5: database = portal_node; break;
			case 6: database = node_portal; break;
		}
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		try {
			cursor = database.openCursor(null, null);
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				cursor.delete();
			}
			if (cursor.count() == 0) {
				System.out.println("finished clearing");
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public void insertToKeywordBlock(String keyword, int[] sgs) throws DatabaseException, UnsupportedEncodingException {
		DatabaseEntry key = new DatabaseEntry(keyword.getBytes("UTF-8"));
		DatabaseEntry value = new DatabaseEntry();
		IntArrayBinding iab = new IntArrayBinding();
		iab.objectToEntry(sgs, value);
		
		keyword_block.put(null, key, value);
	}
	
	public int[] getFromKeywordBlock(String keyword) throws DatabaseException, UnsupportedEncodingException {
		DatabaseEntry key = new DatabaseEntry(keyword.getBytes("UTF-8"));
		DatabaseEntry value = new DatabaseEntry();
		if (keyword_block.get(null, key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			IntArrayBinding iab = new IntArrayBinding();
			int[] sgs = (int[])iab.entryToObject(value);
			return sgs;
		}
		else {
			return null;
		}
	}
	
	public void insertToPortalBlock(int vertex, int[] blocklist) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		IntegerBinding.intToEntry(vertex, key);
		IntArrayBinding iab = new IntArrayBinding();
		iab.objectToEntry(blocklist, value);
		
		portal_block.put(null, key, value);
	}
	
	public int[] getFromPortalBlock(int vertex) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		IntegerBinding.intToEntry(vertex, key);
		if (portal_block.get(null, key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			IntArrayBinding iab = new IntArrayBinding();
			int[] sgs = (int[])iab.entryToObject(value);
			return sgs;
		}
		else {
			return null;
		}
	}
	
	public void insertToKeywordNode(BlockKeyword bk, ArrayList<KNEntry> list) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		BlockKeywordBinding bvb = new BlockKeywordBinding();
		bvb.objectToEntry(bk, key);
		KNListBinding iab = new KNListBinding();
		iab.objectToEntry(list, value);
		
		keyword_node.put(null, key, value);
	}
	
	public KNEntry[] getFromKeywordVertex(BlockKeyword bk) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		BlockKeywordBinding bvb = new BlockKeywordBinding();
		bvb.objectToEntry(bk, key);
		if (keyword_node.get(null, key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			KNListBinding iab = new KNListBinding();
			KNEntry[] sgs = (KNEntry[]) iab.entryToObject(value);
			return sgs;
		}
		else {
			return null;
		}
	}
	
	public void insertToNodeKeyword(BlockVertexKeyword bvk, NKEntry entry) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		BlockVertexKeywordBinding bvb = new BlockVertexKeywordBinding();
		bvb.objectToEntry(bvk, key);
		NKMapBinding iab = new NKMapBinding();
		iab.objectToEntry(entry, value);
		
		node_keyword.put(null, key, value);
	}
	
	public NKEntry getFromVertexKeyword(BlockVertexKeyword bvk) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		BlockVertexKeywordBinding bvb = new BlockVertexKeywordBinding();
		bvb.objectToEntry(bvk, key);
		if (node_keyword.get(null, key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			NKMapBinding iab = new NKMapBinding();
			NKEntry nke = (NKEntry) iab.entryToObject(value);
			return nke;
		}
		else {
			return null;
		}
	}
	
	public void insertToPortalNode(SimpleSubgraphVertex source, ArrayList<PNEntry> list) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		SubgraphVertexBinding svb = new SubgraphVertexBinding();
		svb.objectToEntry(source, key);
		PNListBinding db = new PNListBinding();
		db.objectToEntry(list, value);
		
		portal_node.put(null, key, value);
	}
	
	public PNEntry[] getFromPortalVertex(SimpleSubgraphVertex source) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		SubgraphVertexBinding svb = new SubgraphVertexBinding();
		svb.objectToEntry(source, key);
		if (portal_node.get(null, key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			PNListBinding iab = new PNListBinding();
			PNEntry[] sgs = (PNEntry[]) iab.entryToObject(value);
			return sgs;
		}
		else {
			return null;
		}
	}
	
	public void insertToVertexPortal(SimpleSubgraphVertex source, double distance) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		SubgraphVertexBinding svb = new SubgraphVertexBinding();
		svb.objectToEntry(source, key);
		DoubleBinding db = new DoubleBinding();
		db.objectToEntry(distance, value);
		
		node_portal.put(null, key, value);
	}
	
	public double getFromVertexPortal(SimpleSubgraphVertex source) throws DatabaseException {
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		SubgraphVertexBinding svb = new SubgraphVertexBinding();
		svb.objectToEntry(source, key);
		if (node_portal.get(null, key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			double distance = DoubleBinding.entryToDouble(value);
			return distance;
		}
		else {
			return Double.MAX_VALUE;
		}
	}
	
	public void printKeywordBlock(FileWriter fw) {
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		SubGraphBinding sgb = new SubGraphBinding();
		
		try {
			cursor = keyword_block.openCursor(null, null);
			int id = 0;
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				id++;
				String keyword = new String(key.getData(), "UTF-8");
				IntArrayBinding iab = new IntArrayBinding();
				int[] list = (int[])iab.entryToObject(value);
				fw.write(id + "  ");
				fw.write(keyword + " ( ");
				for (int i = 0; i < list.length; i++) {
					fw.write(list[i] + "  ");
				}
				fw.write(")");
				fw.write("\n");
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		catch (IOException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public void printPortalBlock(FileWriter fw) {
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		IntArrayBinding iab = new IntArrayBinding();
		
		try {
			cursor = portal_block.openCursor(null, null);
			int id = 0;
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				int portal = IntegerBinding.entryToInt(key);
				int[] list = (int[])iab.entryToObject(value);
				fw.write(portal + " ( ");
				for (int i = 0; i < list.length; i++) {
					fw.write(list[i] + " ");
				}
				fw.write(")");
				fw.write("\n");
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		catch (IOException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public void printKeywordNode(FileWriter fw) {
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		BlockKeywordBinding bkb = new BlockKeywordBinding();
		KNListBinding iab = new KNListBinding();
		
		try {
			cursor = keyword_node.openCursor(null, null);
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				BlockKeyword bk = (BlockKeyword) bkb.entryToObject(key);
				int block = bk.getBlock();
				String keyword = bk.getKeyword();
				KNEntry[] list = (KNEntry[]) iab.entryToObject(value);
				fw.write(block + "  ");
				fw.write(keyword + " ( ");
				for (int i = 0; i < list.length; i++) {
					fw.write(list[i].getNode() + " : ");
					fw.write(list[i].getDistance() + ", ");
					fw.write(list[i].getKnode() + ", ");
					fw.write(list[i].getFirst() + " ");
				}
				fw.write(")");
				fw.write("\n");
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		catch (IOException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public void printNodeKeyword(FileWriter fw) {
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		BlockVertexKeywordBinding bkb = new BlockVertexKeywordBinding();
		NKMapBinding iab = new NKMapBinding();
		
		try {
			cursor = node_keyword.openCursor(null, null);
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				BlockVertexKeyword bk = (BlockVertexKeyword) bkb.entryToObject(key);
				int block = bk.getBlock();
				int vertex = bk.getVertex();
				String keyword = bk.getKeyword();
				NKEntry nke = (NKEntry) iab.entryToObject(value);
				fw.write(block + "  ");
				fw.write(vertex + "  ");
				fw.write(keyword + " ( ");
				fw.write(nke.getDistance() + " : ");
				fw.write(nke.getKnode() + ", ");
				fw.write(nke.getFirst() + " )");
				fw.write("\n");
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		catch (IOException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public void printPortalNode(FileWriter fw) {
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		SubgraphVertexBinding svb = new SubgraphVertexBinding();
		PNListBinding iab = new PNListBinding();
		
		try {
			cursor = portal_node.openCursor(null, null);
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				SimpleSubgraphVertex bk = (SimpleSubgraphVertex) svb.entryToObject(key);
				int block = bk.getSubgraph_id();
				int vertex = bk.getVertex_id();
				PNEntry[] list = (PNEntry[]) iab.entryToObject(value);
				fw.write(block + "  ");
				fw.write(vertex + " ( ");
				for (int i = 0; i < list.length; i++) {
					fw.write(list[i].getNode() + " : ");
					fw.write(list[i].getDistance() + ", ");
					fw.write(list[i].getFirst() + " ");
				}
				fw.write(")");
				fw.write("\n");
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		catch (IOException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
	public void printNodePortal(FileWriter fw) {
		Cursor cursor = null;
		DatabaseEntry key = new DatabaseEntry();
		DatabaseEntry value = new DatabaseEntry();
		SubgraphVertexBinding svb = new SubgraphVertexBinding();
		
		try {
			cursor = node_portal.openCursor(null, null);
			while (cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				SimpleSubgraphVertex bk = (SimpleSubgraphVertex) svb.entryToObject(key);
				int block = bk.getSubgraph_id();
				int vertex = bk.getVertex_id();
				DoubleBinding iab = new DoubleBinding();
				double distance = iab.entryToObject(value);
				fw.write(block + "  ");
				fw.write(vertex + " ( ");
				fw.write(distance + " )");
				fw.write("\n");
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		catch (IOException ex) {
			ex.printStackTrace();
			System.err.println("Error: printing subgraphs");
		}
		finally {
			try {
				if (cursor != null) {
					cursor.close();
				}
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
				System.err.println("Error: closing cursor");
			}
		}
	}
	
}
